<template>
  <div class="table-wrap" ref="tableWrapRef">
    <!-- 工具栏 -->
    <div
      class="table-toolbar"
      style="
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 14px 0;
        margin: 14px 0;
      "
    >
      <div style="display: flex; gap: 8px; align-items: center; padding-left: 30px">
        <a-date-picker
          v-model:value="filterMonth"
          picker="month"
          size="middle"
          placeholder="按年月筛选"
          style="width: 160px"
          :getPopupContainer="getPopupContainer"
          :allowClear="true"
        />
        <a-input
          v-model:value="filterKeyword"
          placeholder="按项目名关键字搜索"
          style="width: 220px"
          allow-clear
        />
        <a-button size="middle" @click="clearFilters">清除</a-button>
      </div>

      <div
        style="display: flex; justify-content: flex-end; align-items: center; padding-right: 30px"
      >
        <a-button type="primary" @click.stop="addNewProject">新增项目</a-button>
      </div>
    </div>

    <a-table
      :columns="computedColumns"
      :data-source="pagedData"
      row-key="key"
      bordered
      size="small"
      :scroll="{ x: '1600px', y: tableBodyHeight }"
      class="editable-table"
      :pagination="paginationConfig"
      @change="onTableChange"
    >
      <template #bodyCell="{ column, record }">
        <div
          v-if="column.dataIndex === 'project'"
          :class="['project-cell', isActiveCell(record.key, column.dataIndex) ? 'cell-active' : '']"
          @click.stop="openEditor(record.key, 'project', $event)"
        >
          <div class="project-name">{{ record.project }}</div>
          <div class="project-yearmonth">{{ record.yearMonth }}</div>
        </div>

        <div
          v-else
          :class="['cell-view', getCellClass(record, column.dataIndex)]"
          @click.stop="openEditor(record.key, column.dataIndex, $event)"
        >
          <span
            class="state-dot"
            :class="stateClass(getCell(record, column.dataIndex))"
            :title="stateTitle(getCell(record, column.dataIndex))"
            aria-hidden="true"
          ></span>

          <div class="cell-content">
            <div class="person">{{ displayPerson(getCell(record, column.dataIndex)) }}</div>
            <div class="date">{{ displayDate(getCell(record, column.dataIndex)) }}</div>
          </div>
        </div>
      </template>
    </a-table>

    <!-- 编辑弹窗 -->
    <div
      v-if="editor.visible"
      class="cell-editor-popup"
      :style="{
        left: editor.left + 'px',
        top: editor.top + 'px',
        transformOrigin: editor.transformOrigin,
      }"
      ref="popupRef"
      @keydown.capture.stop="onPopupKeydown"
      @click.stop
      @mousedown.stop
    >
      <div
        class="popup-arrow"
        :class="editor.placement"
        :style="{
          left: editor.arrowLeft ? editor.arrowLeft + 'px' : 'auto',
          top: editor.arrowTop ? editor.arrowTop + 'px' : 'auto',
        }"
      ></div>

      <div class="popup-content">
        <template v-if="editor.mode === 'project'">
          <div class="form-row">
            <label>项目名</label>
            <a-input ref="projectInputRef" v-model:value="editorValues.project" size="small" />
          </div>
          <div class="form-row">
            <label>时间</label>
            <div>
              <a-date-picker
                v-model:value="editorValues.yearMonth"
                picker="month"
                size="small"
                style="width: 160px"
                placeholder="选择年月"
                :format="'YYYY-MM'"
                :getPopupContainer="getPopupContainer"
                :dropdownStyle="{ zIndex: 1800 }"
              />
            </div>
          </div>
        </template>

        <template v-else>
          <div class="form-row">
            <label style="width: 60px">姓名</label>
            <a-select
              v-model:value="editorValues.person"
              :options="personOptions"
              show-search
              allow-clear
              mode="multiple"
              filterOption
              placeholder="选择人员（多选）"
              style="min-width: 220px"
              :getPopupContainer="getPopupContainer"
              :dropdownStyle="{ zIndex: 1600 }"
              @popupVisibleChange="onSelectDropdownVisibleChange"
              @change="onSelectChange"
            />
          </div>

          <div class="form-row">
            <label style="width: 60px">计划日期</label>
            <div @wheel.prevent="onWheelPlanMonth">
              <a-input-number
                v-model:value="editorValues.planMonth"
                :min="1"
                :max="12"
                size="small"
              />
            </div>
            <label style="width: 60px; margin-left: 8px">月</label>
            <div @wheel.prevent="onWheelPlanDay">
              <a-input-number
                v-model:value="editorValues.planDay"
                :min="1"
                :max="31"
                size="small"
              />
            </div>
            <label style="width: 30px; margin-left: 8px">日</label>
          </div>

          <div class="form-row">
            <label style="width: 60px">实际日期</label>
            <div @wheel.prevent="onWheelActualMonth">
              <a-input-number
                v-model:value="editorValues.actualMonth"
                :min="1"
                :max="12"
                size="small"
              />
            </div>
            <label style="width: 60px; margin-left: 8px">月</label>
            <div @wheel.prevent="onWheelActualDay">
              <a-input-number
                v-model:value="editorValues.actualDay"
                :min="1"
                :max="31"
                size="small"
              />
            </div>
            <label style="width: 30px; margin-left: 8px">日</label>
          </div>

          <!-- 新增：状态选择 -->
          <div class="form-row">
            <label style="width: 60px">状态</label>
            <a-select
              v-model:value="editorValues.state"
              :options="stateOptions"
              placeholder="请选择状态"
              size="small"
              style="min-width: 160px"
              :getPopupContainer="getPopupContainer"
            />
          </div>
        </template>

        <!-- 保存 / 删除 / 取消 并排 -->
        <div
          class="popup-actions"
          style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px"
        >
          <a-button size="small" type="primary" @click="saveEditor">保存</a-button>
          <a-button size="small" danger @click="onDeleteFromEditor">删除</a-button>
          <a-button size="small" @click="cancelEditor">取消</a-button>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref, nextTick, onMounted, onBeforeUnmount, computed, h } from 'vue'
import dayjs from 'dayjs'
import { Modal } from 'ant-design-vue'
import * as api from '@/services/api'
import type { TaskPayload, ProjectPayload } from '@/services/api' // 可选的类型导入

const isSaving = ref(false)
const isDeleting = ref(false)


/* ----------------------- 类型 ----------------------- */
type StateType = 'pending' | 'done' | null

interface CellData {
  person: string | string[] | null
  planMonth: number | null
  planDay: number | null
  actualMonth: number | null
  actualDay: number | null
  state?: StateType
}

const taskKeys = [
  'duijie',
  'zhengti',
  'wuli',
  'wuli_writer',
  'shebei',
  'shebei_writer',
  'tongxin',
  'tongxin_writer',
  'yingyong',
  'yingyong_writer',
  'zhidu',
  'zhidu_writer',
  'fangan_communicating',
  'fangan_report',
  'beian',
] as const
type TaskKey = (typeof taskKeys)[number]

type RowData = {
  key: string
  project: string
  yearMonth: string
  tasks: Record<TaskKey, CellData>
  _isNew?: boolean
}

/* ----------------------- 示例数据（20 条，默认 state: 'pending'） ----------------------- */
const data = reactive<RowData[]>([
  {
    key: '1',
    project: '项目A',
    yearMonth: '2025-08',
    tasks: {
      duijie: {
        person: '张三',
        planMonth: 8,
        planDay: 5,
        actualMonth: 8,
        actualDay: 6,
        state: 'pending',
      },
      zhengti: {
        person: '李四',
        planMonth: 8,
        planDay: 6,
        actualMonth: 8,
        actualDay: 7,
        state: 'done',
      },
      wuli: {
        person: '王五',
        planMonth: 8,
        planDay: 7,
        actualMonth: 8,
        actualDay: 8,
        state: 'pending',
      },
      wuli_writer: {
        person: '赵六',
        planMonth: 8,
        planDay: 8,
        actualMonth: 8,
        actualDay: 9,
        state: 'done',
      },
      shebei: {
        person: '钱七',
        planMonth: 8,
        planDay: 9,
        actualMonth: 8,
        actualDay: 10,
        state: 'pending',
      },
      shebei_writer: {
        person: '孙八',
        planMonth: 8,
        planDay: 10,
        actualMonth: 8,
        actualDay: 11,
        state: 'pending',
      },
      tongxin: {
        person: '周九',
        planMonth: 8,
        planDay: 11,
        actualMonth: 8,
        actualDay: 12,
        state: 'done',
      },
      tongxin_writer: {
        person: '吴十',
        planMonth: 8,
        planDay: 12,
        actualMonth: 8,
        actualDay: 13,
        state: 'pending',
      },
      yingyong: {
        person: '郑十一',
        planMonth: 8,
        planDay: 13,
        actualMonth: 8,
        actualDay: 14,
        state: 'pending',
      },
      yingyong_writer: {
        person: '冯十二',
        planMonth: 8,
        planDay: 14,
        actualMonth: 8,
        actualDay: 15,
        state: 'done',
      },
      zhidu: {
        person: '陈十三',
        planMonth: 8,
        planDay: 15,
        actualMonth: 8,
        actualDay: 16,
        state: 'pending',
      },
      zhidu_writer: {
        person: '褚十四',
        planMonth: 8,
        planDay: 16,
        actualMonth: 8,
        actualDay: 17,
        state: 'pending',
      },
      fangan_communicating: {
        person: '卢十五',
        planMonth: 8,
        planDay: 17,
        actualMonth: 8,
        actualDay: 18,
        state: 'pending',
      },
      fangan_report: {
        person: '林十六',
        planMonth: 8,
        planDay: 18,
        actualMonth: 8,
        actualDay: 19,
        state: 'done',
      },
      beian: {
        person: '韩十七',
        planMonth: 8,
        planDay: 19,
        actualMonth: 8,
        actualDay: 20,
        state: 'pending',
      },
    },
  },
  {
    key: '2',
    project: '项目B',
    yearMonth: '2025-08',
    tasks: {
      duijie: {
        person: '李四',
        planMonth: 8,
        planDay: 3,
        actualMonth: 8,
        actualDay: 4,
        state: 'pending',
      },
      zhengti: {
        person: '张三',
        planMonth: 8,
        planDay: 4,
        actualMonth: 8,
        actualDay: 5,
        state: 'pending',
      },
      wuli: {
        person: '王五',
        planMonth: 8,
        planDay: 5,
        actualMonth: 8,
        actualDay: 6,
        state: 'pending',
      },
      wuli_writer: {
        person: '赵六',
        planMonth: 8,
        planDay: 6,
        actualMonth: 8,
        actualDay: 7,
        state: 'pending',
      },
      shebei: {
        person: '钱七',
        planMonth: 8,
        planDay: 7,
        actualMonth: 8,
        actualDay: 8,
        state: 'done',
      },
      shebei_writer: {
        person: '孙八',
        planMonth: 8,
        planDay: 8,
        actualMonth: 8,
        actualDay: 9,
        state: 'pending',
      },
      tongxin: {
        person: '周九',
        planMonth: 8,
        planDay: 9,
        actualMonth: 8,
        actualDay: 10,
        state: 'pending',
      },
      tongxin_writer: {
        person: '吴十',
        planMonth: 8,
        planDay: 10,
        actualMonth: 8,
        actualDay: 11,
        state: 'pending',
      },
      yingyong: {
        person: '郑十一',
        planMonth: 8,
        planDay: 11,
        actualMonth: 8,
        actualDay: 12,
        state: 'pending',
      },
      yingyong_writer: {
        person: '冯十二',
        planMonth: 8,
        planDay: 12,
        actualMonth: 8,
        actualDay: 13,
        state: 'pending',
      },
      zhidu: {
        person: '陈十三',
        planMonth: 8,
        planDay: 13,
        actualMonth: 8,
        actualDay: 14,
        state: 'pending',
      },
      zhidu_writer: {
        person: '褚十四',
        planMonth: 8,
        planDay: 14,
        actualMonth: 8,
        actualDay: 15,
        state: 'pending',
      },
      fangan_communicating: {
        person: '卢十五',
        planMonth: 8,
        planDay: 15,
        actualMonth: 8,
        actualDay: 16,
        state: 'pending',
      },
      fangan_report: {
        person: '林十六',
        planMonth: 8,
        planDay: 16,
        actualMonth: 8,
        actualDay: 17,
        state: 'pending',
      },
      beian: {
        person: '韩十七',
        planMonth: 8,
        planDay: 17,
        actualMonth: 8,
        actualDay: 18,
        state: 'done',
      },
    },
  },
])

for (let i = data.length + 1; i <= 20; i++) {
  const key = String(i)
  const m = i % 12 || 12
  const tasks: Record<TaskKey, CellData> = {
    duijie: {
      person: '张三',
      planMonth: m,
      planDay: (i % 28) + 1,
      actualMonth: m,
      actualDay: (i % 28) + 2,
      state: 'pending',
    },
    zhengti: {
      person: '李四',
      planMonth: m,
      planDay: ((i + 1) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 1) % 28) + 2,
      state: 'pending',
    },
    wuli: {
      person: '王五',
      planMonth: m,
      planDay: ((i + 2) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 2) % 28) + 2,
      state: 'pending',
    },
    wuli_writer: {
      person: '赵六',
      planMonth: m,
      planDay: ((i + 3) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 3) % 28) + 2,
      state: 'pending',
    },
    shebei: {
      person: '钱七',
      planMonth: m,
      planDay: ((i + 4) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 4) % 28) + 2,
      state: 'pending',
    },
    shebei_writer: {
      person: '孙八',
      planMonth: m,
      planDay: ((i + 5) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 5) % 28) + 2,
      state: 'pending',
    },
    tongxin: {
      person: '周九',
      planMonth: m,
      planDay: ((i + 6) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 6) % 28) + 2,
      state: 'pending',
    },
    tongxin_writer: {
      person: '吴十',
      planMonth: m,
      planDay: ((i + 7) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 7) % 28) + 2,
      state: 'pending',
    },
    yingyong: {
      person: '郑十一',
      planMonth: m,
      planDay: ((i + 8) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 8) % 28) + 2,
      state: 'pending',
    },
    yingyong_writer: {
      person: '冯十二',
      planMonth: m,
      planDay: ((i + 9) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 9) % 28) + 2,
      state: 'pending',
    },
    zhidu: {
      person: '陈十三',
      planMonth: m,
      planDay: ((i + 10) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 10) % 28) + 2,
      state: 'pending',
    },
    zhidu_writer: {
      person: '褚十四',
      planMonth: m,
      planDay: ((i + 11) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 11) % 28) + 2,
      state: 'pending',
    },
    fangan_communicating: {
      person: '卢十五',
      planMonth: m,
      planDay: ((i + 12) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 12) % 28) + 2,
      state: 'pending',
    },
    fangan_report: {
      person: '林十六',
      planMonth: m,
      planDay: ((i + 13) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 13) % 28) + 2,
      state: 'pending',
    },
    beian: {
      person: '韩十七',
      planMonth: m,
      planDay: ((i + 14) % 28) + 1,
      actualMonth: m,
      actualDay: ((i + 14) % 28) + 2,
      state: 'pending',
    },
  }
  data.push({
    key,
    project: `项目${i}`,
    yearMonth: i % 2 === 0 ? '2025-08' : '2025-09',
    tasks,
  })
}

/* ----------------------- 人员候选 ----------------------- */
const personOptions = [
  { label: '张三', value: '张三' },
  { label: '李四', value: '李四' },
  { label: '王五', value: '王五' },
  { label: '赵六', value: '赵六' },
  { label: '钱七', value: '钱七' },
  { label: '孙八', value: '孙八' },
  { label: '周九', value: '周九' },
  { label: '吴十', value: '吴十' },
]

/* 状态选项 */
const stateOptions = [
  { label: '未完成', value: 'pending' },
  { label: '完成', value: 'done' },
]

/* ----------------------- 编辑器状态与数据 ----------------------- */
const tableWrapRef = ref<HTMLElement | null>(null)
const popupRef = ref<HTMLElement | null>(null)
const projectInputRef = ref<any>(null)

const activeCell = ref<{ rowKey: string; colKey: string } | null>(null)

type EditorColKey = TaskKey | 'project'
const editor = reactive({
  visible: false,
  rowKey: '' as string,
  colKey: '' as EditorColKey,
  left: 0,
  top: 0,
  placement: 'right' as 'right' | 'left' | 'top' | 'bottom',
  arrowLeft: 12 as number | null,
  arrowTop: null as number | null,
  transformOrigin: 'center top',
  mode: 'cell' as 'cell' | 'project',
})

const editorValues = reactive({
  // project
  project: '',
  yearMonth: null as any, // dayjs|null

  // cell: people + plan/actual + state
  person: [] as string[] | string | null,
  planMonth: null as number | null,
  planDay: null as number | null,
  actualMonth: null as number | null,
  actualDay: null as number | null,
  state: null as StateType,
})

/* ----------------------- 筛选/分页 ----------------------- */
const filterMonth = ref<any>(null)
const filterKeyword = ref('')
const tableBodyHeight = 750
const currentPage = ref(1)
const pageSize = ref(10)

const filteredData = computed(() => {
  const kw = filterKeyword.value.trim().toLowerCase()
  const ym = filterMonth.value
    ? (filterMonth.value.format?.('YYYY-MM') ?? String(filterMonth.value))
    : ''
  return data.filter((r) => {
    if (r._isNew) return true
    if (ym) {
      if (!r.yearMonth) return false
      if (r.yearMonth !== ym) return false
    }
    if (kw) {
      if (!r.project) return false
      if (r.project.toLowerCase().indexOf(kw) === -1) return false
    }
    return true
  })
})

const paginationConfig = computed(() => ({
  current: currentPage.value,
  pageSize: pageSize.value,
  total: filteredData.value.length,
  showSizeChanger: true,
  pageSizeOptions: ['10', '20', '50'],
  showQuickJumper: true,
  showTotal: (total: number) => `共 ${total} 条`,
}))

const pagedData = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  const end = start + pageSize.value
  return filteredData.value.slice(start, end)
})

function onTableChange(pagination: any) {
  if (pagination) {
    currentPage.value = pagination.current ?? currentPage.value
    pageSize.value = pagination.pageSize ?? pageSize.value
  }
}
function clearFilters() {
  filterMonth.value = null
  filterKeyword.value = ''
  currentPage.value = 1
}

/* ----------------------- 排序（在表头控制） ----------------------- */
const sortDirection = ref<'asc' | 'desc'>('asc')
function toggleSortDirection() {
  sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
  sortAllData(sortDirection.value)
  currentPage.value = 1
  nextTick(() => {
    const wrap = tableWrapRef.value
    if (wrap) wrap.scrollIntoView({ behavior: 'smooth', block: 'start' })
  })
}
function sortAllData(direction: 'asc' | 'desc' = 'asc') {
  data.sort((a, b) => {
    const aYM =
      typeof a.yearMonth === 'string' && a.yearMonth.trim() !== '' ? a.yearMonth.trim() : ''
    const bYM =
      typeof b.yearMonth === 'string' && b.yearMonth.trim() !== '' ? b.yearMonth.trim() : ''
    const aHas = !!aYM && dayjs(aYM + '-01').isValid()
    const bHas = !!bYM && dayjs(bYM + '-01').isValid()

    if (!aHas && bHas) return -1
    if (aHas && !bHas) return 1

    const projectCmp = (x: RowData, y: RowData) => {
      const pa = (x.project || '').toLowerCase()
      const pb = (y.project || '').toLowerCase()
      if (pa < pb) return -1
      if (pa > pb) return 1
      return 0
    }

    if (!aHas && !bHas) return projectCmp(a, b)

    const da = dayjs(aYM + '-01')
    const db = dayjs(bYM + '-01')
    const diff = da.valueOf() - db.valueOf()
    if (diff === 0) return projectCmp(a, b)
    return direction === 'asc' ? diff : -diff
  })
}

/* computedColumns：自定义 title node 可点击 */
const baseColumns = [
  { title: '对接', dataIndex: 'duijie', key: 'duijie', width: 70 },
  { title: '整体', dataIndex: 'zhengti', key: 'zhengti', width: 70 },
  { title: '物理现场', dataIndex: 'wuli', key: 'wuli', width: 70 },
  { title: '物理编写', dataIndex: 'wuli_writer', key: 'wuli_writer', width: 70 },
  { title: '设备现场', dataIndex: 'shebei', key: 'shebei', width: 70 },
  { title: '设备编写', dataIndex: 'shebei_writer', key: 'shebei_writer', width: 70 },
  { title: '通信现场', dataIndex: 'tongxin', key: 'tongxin', width: 70 },
  { title: '通信编写', dataIndex: 'tongxin_writer', key: 'tongxin_writer', width: 70 },
  { title: '应用现场', dataIndex: 'yingyong', key: 'yingyong', width: 70 },
  { title: '应用编写', dataIndex: 'yingyong_writer', key: 'yingyong_writer', width: 70 },
  { title: '制度对接', dataIndex: 'zhidu', key: 'zhidu', width: 70 },
  { title: '制度编写', dataIndex: 'zhidu_writer', key: 'zhidu_writer', width: 70 },
  { title: '方案对接', dataIndex: 'fangan_communicating', key: 'fangan_communicating', width: 70 },
  { title: '方案报告', dataIndex: 'fangan_report', key: 'fangan_report', width: 70 },
  { title: '备案准备', dataIndex: 'beian', key: 'beian', width: 70 },
]

const computedColumns = computed(() => {
  const arrow = sortDirection.value === 'asc' ? '▲' : '▼'
  const titleNode = () =>
    h(
      'div',
      {
        role: 'button',
        tabindex: 0,
        style: {
          display: 'inline-flex',
          alignItems: 'center',
          gap: '6px',
          cursor: 'pointer',
          userSelect: 'none',
        },
        onClick: (e: MouseEvent) => {
          e.stopPropagation()
          toggleSortDirection()
        },
        onKeydown: (e: KeyboardEvent) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault()
            toggleSortDirection()
          }
        },
      },
      [
        h('span', { style: { fontWeight: 600 } }, '项目 / 时间'),
        h('span', { style: { fontSize: '12px', color: '#666' } }, arrow),
      ],
    )

  const projectCol = {
    title: titleNode,
    dataIndex: 'project',
    key: 'project',
    width: 250,
    fixed: 'left',
  }
  return [projectCol, ...baseColumns]
})

/* ----------------------- 帮助函数：展示 / 规范化 ----------------------- */
function isActiveCell(rowKey: string, colKey: string) {
  return (
    activeCell.value && activeCell.value.rowKey === rowKey && activeCell.value.colKey === colKey
  )
}
function getCell(record: RowData, dataIndex: string): CellData | undefined {
  if (dataIndex === 'project') return undefined
  return record.tasks[dataIndex as TaskKey]
}
/* ---------- helper: 把可能的 person 值归一化成字符串或字符串数组 ---------- */
function normalizePersonValue(raw: any): string[] {
  if (raw == null) return []
  if (Array.isArray(raw)) {
    return raw
      .map((v) => {
        if (v == null) return ''
        if (typeof v === 'string' || typeof v === 'number') return String(v)
        if (typeof v === 'object') return String(v.value ?? v.label ?? JSON.stringify(v))
        return String(v)
      })
      .filter(Boolean)
  }
  if (typeof raw === 'string' || typeof raw === 'number') return [String(raw)]
  if (typeof raw === 'object') return [String(raw.value ?? raw.label ?? JSON.stringify(raw))]
  return [String(raw)]
}

/* 更稳健的 displayPerson */
function displayPerson(cell: any) {
  if (cell == null) return ''
  const p = cell && cell.person !== undefined ? cell.person : cell
  if (p == null) return ''
  if (Array.isArray(p)) {
    return p
      .map((v) => {
        if (v == null) return ''
        if (typeof v === 'string' || typeof v === 'number') return String(v)
        if (typeof v === 'object') return String(v.value ?? v.label ?? JSON.stringify(v))
        return String(v)
      })
      .filter(Boolean)
      .join(',')
  }
  if (typeof p === 'string' || typeof p === 'number') return String(p)
  if (typeof p === 'object') return String(p.value ?? p.label ?? JSON.stringify(p))
  return String(p)
}

function displayDate(cell: any) {
  if (!cell) return ''
  const noPerson = !cell.person || (Array.isArray(cell.person) && cell.person.length === 0)
  if (noPerson) return ''
  const pm = cell.planMonth ?? null
  const pd = cell.planDay ?? null
  const am = cell.actualMonth ?? null
  const ad = cell.actualDay ?? null
  const plan = pm && pd ? `${String(pm).padStart(2, '0')}.${String(pd).padStart(2, '0')}` : ''
  const actual = am && ad ? `${String(am).padStart(2, '0')}.${String(ad).padStart(2, '0')}` : ''
  return [plan, actual].filter(Boolean).join(' / ')
}

/* ----------------------- 单元格类名生成（根据是否被选中 + 数据 + state） ----------------------- */
function getCellClass(record: RowData, colKey: string) {
  // 如果不是激活的单元格，不返回 active 类（保持默认/hover）
  if (!isActiveCell(record.key, colKey)) return ''
  const cell = getCell(record, colKey)
  // 判断是否有人员（代表有数据）
  const p = cell && cell.person !== undefined ? cell.person : null
  const hasPerson =
    p != null && (!Array.isArray(p) ? String(p).trim() !== '' : Array.isArray(p) && p.length > 0)

  if (!hasPerson) {
    // 无人员数据：仍然使用蓝色高亮（保持以前行为）
    return 'cell-active-empty'
  }

  // 有人员：根据 state 决定颜色
  const s: StateType = (cell && (cell.state as StateType)) ?? 'pending'
  if (s === 'done') return 'cell-active-done'
  return 'cell-active-pending'
}

/* ----------------------- 打开编辑器（含定位 & 预置） ----------------------- */
function openEditor(rowKey: string, colKey: string, evt: MouseEvent) {
  const target = evt.currentTarget as HTMLElement
  if (!target) return
  const record = data.find((r) => r.key === rowKey)
  if (!record) return
  const rect = target.getBoundingClientRect()
  openEditorAtRect(record, colKey as EditorColKey, rect)
}

function openEditorAtRect(record: RowData, colKey: EditorColKey, anchorRect: DOMRect) {
  activeCell.value = { rowKey: record.key, colKey }

  if (colKey === 'project') {
    editor.mode = 'project'
    editorValues.project = record.project || ''
    editorValues.yearMonth = record.yearMonth ? dayjs(record.yearMonth + '-01') : null
  } else {
    editor.mode = 'cell'
    const cur = getCell(record, colKey) || {
      person: null,
      planMonth: null,
      planDay: null,
      actualMonth: null,
      actualDay: null,
      state: null,
    }
    // person
    const curPerson = cur.person
    if (Array.isArray(curPerson)) editorValues.person = [...curPerson]
    else if (typeof curPerson === 'string' && curPerson.includes(','))
      editorValues.person = curPerson.split(',').map((s) => s.trim())
    else editorValues.person = curPerson ? [String(curPerson)] : []

    // plan/actual - 允许为 null
    editorValues.planMonth = cur.planMonth ?? null
    editorValues.planDay = cur.planDay ?? null
    editorValues.actualMonth = cur.actualMonth ?? null
    editorValues.actualDay = cur.actualDay ?? null
    // state
    editorValues.state = (cur.state as StateType) ?? 'pending' // 默认未完成
  }

  editor.rowKey = record.key
  editor.colKey = colKey
  editor.visible = true

  editor.placement = 'right'
  editor.left = Math.round(anchorRect.right + 12)
  editor.top = Math.round(anchorRect.top + anchorRect.height / 2 - 80)
  editor.arrowLeft = -8

  nextTick(() => {
    adjustPopupPosition(anchorRect)
    if (editor.mode === 'project') focusProjectInput()
  })
}

/* ----------------------- 弹窗位置调整（同之前逻辑） ----------------------- */
function adjustPopupPosition(anchorRect: DOMRect) {
  const popup = popupRef.value
  if (!popup) return
  const popRect = popup.getBoundingClientRect()
  const vw = window.innerWidth
  const vh = window.innerHeight

  let left = anchorRect.right + 12
  let top = anchorRect.top + anchorRect.height / 2 - popRect.height / 2
  let placement: typeof editor.placement = 'right'

  if (left + popRect.width > vw - 8) {
    const leftCandidate = anchorRect.left - 12 - popRect.width
    if (leftCandidate > 8) {
      left = leftCandidate
      placement = 'left'
    } else {
      const topCandidate = anchorRect.top - 12 - popRect.height
      if (topCandidate > 8) {
        left = anchorRect.left + anchorRect.width / 2 - popRect.width / 2
        top = topCandidate
        placement = 'top'
      } else {
        const bottomCandidate = anchorRect.bottom + 12
        left = Math.min(
          Math.max(8, anchorRect.left + anchorRect.width / 2 - popRect.width / 2),
          vw - popRect.width - 8,
        )
        top = bottomCandidate
        placement = 'bottom'
      }
    }
  }

  if (top < 8) top = 8
  if (top + popRect.height > vh - 8) top = vh - popRect.height - 8

  let arrowLeft = null
  let arrowTop = null
  if (placement === 'right' || placement === 'left') {
    arrowTop = anchorRect.top + anchorRect.height / 2 - top - 6
    if (arrowTop < 8) arrowTop = 8
    if (arrowTop > popRect.height - 16) arrowTop = popRect.height - 16
    arrowLeft = placement === 'right' ? -8 : popRect.width - 8
  } else {
    arrowLeft = anchorRect.left + anchorRect.width / 2 - left - 6
    if (arrowLeft < 8) arrowLeft = 8
    if (arrowLeft > popRect.width - 16) arrowLeft = popRect.width - 16
    arrowTop = placement === 'top' ? popRect.height - 8 : -8
  }

  editor.left = Math.round(left)
  editor.top = Math.round(top)
  editor.placement = placement
  editor.arrowLeft = arrowLeft
  editor.arrowTop = arrowTop
  editor.transformOrigin =
    placement === 'top'
      ? 'center bottom'
      : placement === 'bottom'
        ? 'center top'
        : placement === 'left'
          ? 'right center'
          : 'left center'
}

/* ----------------------- 保存 / 删除 / 取消 ----------------------- */
async function saveEditor() {
  if (isSaving.value) return
  isSaving.value = true

  const rowIndex = data.findIndex((r) => r.key === editor.rowKey)
  if (rowIndex === -1) {
    editor.visible = false
    activeCell.value = null
    isSaving.value = false
    return
  }
  const row = data[rowIndex]

  try {
    if (editor.mode === 'project') {
      // 1) 本地更新
      const prev = { project: row.project, yearMonth: row.yearMonth, _isNew: row._isNew }
      row.project = editorValues.project
      row.yearMonth = editorValues.yearMonth
        ? editorValues.yearMonth.format?.('YYYY-MM') ?? String(editorValues.yearMonth)
        : ''
      if (row._isNew) {
        // keep _isNew until server returns success
      }

      // 2) 如果需要同步到后端：如果是新行 -> createProject；否则 updateProject
      if (row._isNew) {
        // Prepare payload for create; use normalized structure if you have normalizeRowToBackend
        const payload: ProjectPayload = {
          project: row.project,
          yearMonth: row.yearMonth || null,
          tasks: row.tasks as any,
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
        }
        // call backend - this returns created object (should contain id)
        const created = await api.createProject(payload)
        // replace key (如果后端返回 id/key)
        if (created?.id ?? created?.key) {
          const newKey = created.id ?? created.key
          // 替换本地 key
          row.key = String(newKey)
        }
        // remove temporary marker
        delete row._isNew
      } else {
        // 更新整行（可改为仅 patch changed fields）
        const payload: ProjectPayload = {
          project: row.project,
          yearMonth: row.yearMonth || null,
          tasks: row.tasks as any,
          updatedAt: new Date().toISOString(),
        }
        // note: api.updateProject expects projectId
        await api.updateProject(row.key, payload)
      }

      // 3) 本地排序/页码更新（保留你原逻辑）
      sortAllData(sortDirection.value)
      const newIndex = data.findIndex((r) => r.key === row.key)
      if (newIndex >= 0) {
        const page = Math.floor(newIndex / pageSize.value) + 1
        currentPage.value = page
        await nextTick()
        const wrap = tableWrapRef.value
        if (wrap) {
          const cell = wrap.querySelector(`[data-row-key="${row.key}"] .project-cell`) as HTMLElement | null
          if (cell) cell.scrollIntoView({ block: 'center', behavior: 'smooth' })
        }
      }
    } else {
      // cell mode: update person / plan / actual / state locally
      const raw = editorValues.person
      const personSaved = normalizePersonValue(raw)
      const pm = editorValues.planMonth ?? null
      const pd = editorValues.planDay ?? null
      const am = editorValues.actualMonth ?? null
      const ad = editorValues.actualDay ?? null
      const st: StateType = editorValues.state ?? 'pending'
      const key = editor.colKey as TaskKey

      // 保存前的快照（以便失败回滚）
      const prevCell = row.tasks[key] ? { ...row.tasks[key] } : undefined

      // 本地更新
      row.tasks[key] = row.tasks[key] || ({} as any)
      row.tasks[key].person = personSaved
      row.tasks[key].planMonth = pm
      row.tasks[key].planDay = pd
      row.tasks[key].actualMonth = am
      row.tasks[key].actualDay = ad
      row.tasks[key].state = st

      // 发起后端 patch
      const payload: TaskPayload = {
        person: personSaved,
        planMonth: pm,
        planDay: pd,
        actualMonth: am,
        actualDay: ad,
        state: st,
        updatedAt: new Date().toISOString(),
      }

      // 如果后端要求 createdAt for brand-new task，可处理但这里用 patch
      await api.patchTask(row.key, key, payload)
    }

    // 成功关闭编辑器
    editor.visible = false
    activeCell.value = null
  } catch (err) {
    console.error('保存失败', err)
    // 回滚（尽量把本地状态恢复）
    // 如果在 project 模式创建失败并且是 _isNew，移除那一行
    if (editor.mode === 'project') {
      const r = data.find((x) => x.key === editor.rowKey)
      if (r && r._isNew) {
        const idx = data.findIndex((x) => x.key === editor.rowKey)
        if (idx >= 0) data.splice(idx, 1)
      } else {
        // 如果不是新行，尽量恢复原值（你可以保存 prev 值上面）
      }
    } else {
      // cell 回滚：如果 prevCell 有备份则恢复（示例：这里不完全实现所有回滚细节）
      // row.tasks[key] = prevCell ?? { person: [], planMonth: null, ... }
    }
    // 给用户提示
    Modal.error({
      title: '保存失败',
      content: '与服务器通信时发生错误，请稍后重试。',
    })
  } finally {
    isSaving.value = false
  }
}


/* 编辑器删除：整行或清空该单元格 */
function onDeleteFromEditor() {
  if (!editor.rowKey) return
  const rowIndex = data.findIndex((r) => r.key === editor.rowKey)
  if (rowIndex === -1) {
    editor.visible = false
    activeCell.value = null
    return
  }

  const row = data[rowIndex]

  if (editor.mode === 'project') {
    Modal.confirm({
      title: '删除项目',
      content: '确定要删除该项目？此操作不可撤销。',
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      centered: true,
      async onOk() {
        try {
          isDeleting.value = true
          // 调用后端删除整行（后端返回成功后再从本地移除）
          await api.deleteProject(row.key)
          data.splice(rowIndex, 1)
          const totalPages = Math.max(1, Math.ceil(data.length / pageSize.value))
          if (currentPage.value > totalPages) currentPage.value = totalPages
        } catch (err) {
          console.error('删除项目失败', err)
          Modal.error({ title: '删除失败', content: '删除项目失败，请稍后重试。' })
          throw err // 抛出以便 Modal 处理
        } finally {
          isDeleting.value = false
        }
      },
    })
  } else {
    const key = editor.colKey as TaskKey
    Modal.confirm({
      title: '删除单元格数据',
      content: '确定要删除当前单元格的数据吗？此操作会清空该任务的人员与日期。',
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      centered: true,
      async onOk() {
        try {
          isDeleting.value = true
          // 本地清空
          row.tasks[key] = {
            person: [],
            planMonth: null,
            planDay: null,
            actualMonth: null,
            actualDay: null,
            state: null,
          }
          // 同步后端：删除或置空 task（根据后端接口）
          // 优先删除 API：deleteTask；若后端倾向于 patch 空对象，可使用 patchTask
          await api.deleteTask(row.key, key)
        } catch (err) {
          console.error('删除单元格失败', err)
          Modal.error({ title: '删除失败', content: '删除单元格失败，请稍后重试。' })
          throw err
        } finally {
          isDeleting.value = false
        }
      },
    })
  }

  editor.visible = false
  activeCell.value = null
}


function cancelEditor() {
  editor.visible = false
  activeCell.value = null
}

/* popup 键盘事件 */
function onPopupKeydown(e: KeyboardEvent) {
  if (e.key === 'Enter') saveEditor()
  else if (e.key === 'Escape') cancelEditor()
}

/* 点击页面任意处关闭（忽略下拉） */
function onDocClick(e: MouseEvent) {
  const popup = popupRef.value
  if (!editor.visible) return
  const target = e.target as Node
  if (popup && popup.contains(target)) return
  const el = target as Element
  if (
    el &&
    el.closest &&
    (el.closest('.ant-select-dropdown') || el.closest('.ant-picker-dropdown'))
  )
    return
  cancelEditor()
}
onMounted(() => {
  document.addEventListener('click', onDocClick)
  window.addEventListener('resize', () => (editor.visible = false))
  sortAllData(sortDirection.value)
})
onBeforeUnmount(() => {
  document.removeEventListener('click', onDocClick)
})

/* ----------------------- select/dropdown 辅助 ----------------------- */
const getPopupContainer = (triggerNode?: HTMLElement): HTMLElement =>
  (triggerNode?.ownerDocument?.body ?? document.body) as HTMLElement

function onSelectDropdownVisibleChange(open: boolean) {
  if (!open) return
  nextTick(() => {
    const drops = Array.from(document.querySelectorAll('.ant-select-dropdown')) as HTMLElement[]
    const dropdown = drops
      .reverse()
      .find((d) => d && window.getComputedStyle(d).display !== 'none' && d.offsetParent !== null)
    if (!dropdown || !editor.visible) return
    const ddRect = dropdown.getBoundingClientRect()
    const popupEl = popupRef.value
    if (!popupEl) return
    const popRect = popupEl.getBoundingClientRect()
    const intersect = !(
      ddRect.right < popRect.left ||
      ddRect.left > popRect.right ||
      ddRect.bottom < popRect.top ||
      ddRect.top > popRect.bottom
    )
    if (!intersect) return
    const vw = window.innerWidth
    let newLeft = ddRect.right + 12
    let newTop = popRect.top
    if (newLeft + popRect.width > vw - 8) newLeft = ddRect.left - popRect.width - 12
    if (newLeft < 8) newLeft = 8
    editor.left = Math.round(newLeft)
    editor.top = Math.round(newTop)
    editor.transformOrigin = 'left center'
  })
}
function onSelectChange(val: any) {
  editorValues.person = Array.isArray(val) ? val.map((v) => String(v)) : val ? [String(val)] : []
}

/** 根据单元格数据返回 dot 的样式类名 */
function stateClass(cell: any): string {
  if (!cell) return 'state-hidden'
  const p = cell.person
  const hasPerson = p != null && (!(Array.isArray(p)) ? String(p).trim() !== '' : p.length > 0)
  // 如果没有人员，则不显示角标
  if (!hasPerson) return 'state-hidden'

  const s = (cell.state ?? null) as 'pending' | 'done' | null
  if (s === 'done') return 'state-done'
  // 其余（包括 null 或 'pending'）都视为未完成显示黄色
  return 'state-pending'
}

/** 鼠标悬停时的 Title 文本（可读性） */
function stateTitle(cell: any): string {
  if (!cell) return ''
  const p = cell.person
  const hasPerson = p != null && (!(Array.isArray(p)) ? String(p).trim() !== '' : p.length > 0)
  if (!hasPerson) return ''
  const s = (cell.state ?? null) as 'pending' | 'done' | null
  if (s === 'done') return '已完成'
  return '未完成'
}


/* ----------------------- 滚轮调整（四个输入都支持，包括空值下预置） ----------------------- */
function ensurePlanDateInit() {
  if (editorValues.planMonth == null || editorValues.planDay == null) {
    const today = dayjs()
    editorValues.planMonth = editorValues.planMonth ?? today.month() + 1
    editorValues.planDay = editorValues.planDay ?? today.date()
  }
}
function ensureActualDateInit() {
  if (editorValues.actualMonth == null || editorValues.actualDay == null) {
    const today = dayjs()
    editorValues.actualMonth = editorValues.actualMonth ?? today.month() + 1
    editorValues.actualDay = editorValues.actualDay ?? today.date()
  }
}

function onWheelPlanMonth(e: WheelEvent) {
  ensurePlanDateInit()
  if (editorValues.planMonth == null) return
  const cur = dayjs().month(editorValues.planMonth - 1)
  const step = 1
  const next = e.deltaY > 0 ? cur.subtract(step, 'month') : cur.add(step, 'month')
  editorValues.planMonth = next.month() + 1
}
function onWheelPlanDay(e: WheelEvent) {
  ensurePlanDateInit()
  if (editorValues.planMonth == null || editorValues.planDay == null) return
  const cur = dayjs()
    .month(editorValues.planMonth - 1)
    .date(editorValues.planDay)
  const step = 1
  const next = e.deltaY > 0 ? cur.subtract(step, 'day') : cur.add(step, 'day')
  editorValues.planMonth = next.month() + 1
  editorValues.planDay = next.date()
}
function onWheelActualMonth(e: WheelEvent) {
  ensureActualDateInit()
  if (editorValues.actualMonth == null) return
  const cur = dayjs().month(editorValues.actualMonth - 1)
  const step = 1
  const next = e.deltaY > 0 ? cur.subtract(step, 'month') : cur.add(step, 'month')
  editorValues.actualMonth = next.month() + 1
}
function onWheelActualDay(e: WheelEvent) {
  ensureActualDateInit()
  if (editorValues.actualMonth == null || editorValues.actualDay == null) return
  const cur = dayjs()
    .month(editorValues.actualMonth - 1)
    .date(editorValues.actualDay)
  const step = 1
  const next = e.deltaY > 0 ? cur.subtract(step, 'day') : cur.add(step, 'day')
  editorValues.actualMonth = next.month() + 1
  editorValues.actualDay = next.date()
}

/* ----------------------- 新增项目（插入顶部并打开 project 编辑） ----------------------- */
function createEmptyTasks(): Record<TaskKey, CellData> {
  return taskKeys.reduce(
    (acc, key) => {
      acc[key] = {
        person: [],
        planMonth: null,
        planDay: null,
        actualMonth: null,
        actualDay: null,
        state: 'pending',
      }
      return acc
    },
    {} as Record<TaskKey, CellData>,
  )
}

async function addNewProject() {
  const newKey = String(Date.now())
  const newRow: RowData = {
    key: newKey,
    project: '',
    yearMonth: '',
    tasks: createEmptyTasks(),
    _isNew: true,
  }
  data.unshift(newRow)
  sortAllData(sortDirection.value)
  currentPage.value = 1

  await nextTick()
  await new Promise((resolve) => requestAnimationFrame(resolve))
  await nextTick()

  const wrap = tableWrapRef.value
  let projectCell: HTMLElement | null = null
  if (wrap) {
    projectCell =
      (wrap.querySelector(`[data-row-key="${newKey}"] .project-cell`) as HTMLElement | null) ||
      (wrap.querySelector(
        `.ant-table-tbody tr[data-row-key="${newKey}"] .project-cell`,
      ) as HTMLElement | null) ||
      (wrap.querySelector(
        `.ant-table-row[data-row-key="${newKey}"] .project-cell`,
      ) as HTMLElement | null)
  }

  const record = data.find((r) => r.key === newKey)!
  if (projectCell) {
    const rect = projectCell.getBoundingClientRect()
    openEditorAtRect(record, 'project', rect)

    const tbody = wrap?.querySelector('.ant-table-body') as HTMLElement | null
    if (tbody) {
      const row = tbody.querySelector(`tr[data-row-key="${newKey}"]`) as HTMLElement | null
      if (row) {
        const top = row.offsetTop
        const desired = top - tbody.clientHeight / 2 + row.clientHeight / 2
        tbody.scrollTop = Math.max(0, Math.round(desired))
      }
    } else {
      projectCell.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
    }

    activeCell.value = { rowKey: newKey, colKey: 'project' }
    nextTick(() => focusProjectInput())
  } else {
    const rect = wrap ? wrap.getBoundingClientRect() : new DOMRect(100, 100, 200, 44)
    openEditorAtRect(record, 'project', rect)
    activeCell.value = { rowKey: newKey, colKey: 'project' }
    nextTick(() => focusProjectInput())
  }
}

/* 聚焦 project 输入 */
function focusProjectInput() {
  let tries = 0
  const tryFocus = () => {
    tries++
    const popup = popupRef.value
    if (!popup) {
      if (tries < 6) setTimeout(tryFocus, 50)
      return
    }
    const input = popup.querySelector('input')
    if (input && typeof (input as HTMLInputElement).focus === 'function') {
      try {
        ;(input as HTMLInputElement).focus()
        ;(input as HTMLInputElement).select?.()
      } catch (e) {}
      return
    }
    if (tries < 6) setTimeout(tryFocus, 60)
  }
  nextTick(tryFocus)
}

/* ----------------------- 后端对接说明（位置提示） ----------------------- */
/**
 * - 在 saveEditor() 中保存行或单元格后，可以调用你的 API 将变更发送到后端。
 *   - 若该行为新行（_isNew），建议先调用 createProject(...)，取得后端 id，再替换本地 row.key / 去掉 _isNew。
 *   - 单元格更新可以调用 patchTask(row.key, taskKey, payload)。
 * - 在 onDeleteFromEditor() 中删除整行或清空单元格，也应当同步到后端（DELETE /projects/{id} 或 PUT 清空 task）。
 *
 * 具体字段名（tasks 内 plan/actual/state 的命名）应以后端为准，若不同：
 * - 建议在组件外写一个 normalize 前/后端转换函数（例如 normalizeRowToBackend / normalizeRowFromBackend）
 * - 或在 save/delete 的地方把前端结构转换成后端期望的结构后再发请求。
 */

/* ----------------------- 结束 ----------------------- */
</script>

<style scoped lang="less">
.table-wrap {
  position: relative;
  padding-bottom: 8px;
}

/* 项目列 */
.project-cell {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 6px;
  transition:
    background 0.28s ease,
    box-shadow 0.18s ease;
  border-radius: 4px;
}
.project-name {
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.project-yearmonth {
  font-size: 12px;
  color: #888;
}

/* 单元格 */
.cell-view {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  padding: 6px;
  transition:
    background 0.28s ease,
    box-shadow 0.18s ease;
  border-radius: 4px;
  position: relative;
}
/* 右上角小角标（状态小圆点） */
.state-dot {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0,0,0,0.12);
  border: 1px solid rgba(0,0,0,0.06);
  transition: background-color 0.18s ease, transform 0.12s ease;
  pointer-events: none; /* 不阻塞点击进入单元格 */
  opacity: 1;
}

/* 隐藏（无数据） */
.state-hidden {
  opacity: 0;
  transform: scale(0.6);
}

/* 未完成：黄色（你可以改颜色值以符合设计） */
.state-pending {
  background-color: #f4b400; /* 黄色 */
  border-color: rgba(0,0,0,0.06);
}

/* 完成：绿色 */
.state-done {
  background-color: #28a745; /* 绿色 */
  border-color: rgba(0,0,0,0.06);
}

/* 可选：在 hover/active 时稍放大，增强可见性 */
.cell-view:hover .state-dot,
.cell-view.cell-active-empty .state-dot,
.cell-view.cell-active-pending .state-dot,
.cell-view.cell-active-done .state-dot {
  transform: scale(1.15);
}

.cell-content {
  display: flex;
  flex-direction: column;
}
.person {
  font-size: 13px;
}
.date {
  font-size: 12px;
  color: #666;
  margin-top: 4px;
}

/* 激活：无数据（保留蓝色） */
.cell-active-empty {
  background-image: linear-gradient(90deg, rgba(24, 144, 255, 0.85), rgba(24, 144, 255, 0.15) 60%);
  background-size: 200% 200%;
  animation: gradientShift 3s linear infinite;
  box-shadow: 0 6px 14px rgba(24, 144, 255, 0.08);
  transition:
    box-shadow 0.18s ease,
    background 0.5s ease;
}

/* 激活：未完成（黄色） */
.cell-active-pending {
  background-image: linear-gradient(90deg, rgba(250, 173, 20, 0.9), rgba(250, 200, 80, 0.12) 60%);
  background-size: 200% 200%;
  animation: gradientShift 3s linear infinite;
  box-shadow: 0 6px 14px rgba(250, 173, 20, 0.08);
  transition:
    box-shadow 0.18s ease,
    background 0.5s ease;
}

/* 激活：完成（绿色） */
.cell-active-done {
  background-image: linear-gradient(90deg, rgba(82, 196, 26, 0.9), rgba(150, 230, 120, 0.12) 60%);
  background-size: 200% 200%;
  animation: gradientShift 3s linear infinite;
  box-shadow: 0 6px 14px rgba(82, 196, 26, 0.08);
  transition:
    box-shadow 0.18s ease,
    background 0.5s ease;
}

@keyframes gradientShift {
  0% {
    background-position: 0% 50%;
  }
  33% {
    background-position: 100% 50%;
  }
  66% {
    background-position: 50% 100%;
  }
  100% {
    background-position: 0% 50%;
  }
}

/* 弹窗样式 */
.cell-editor-popup {
  position: fixed;
  z-index: 1500;
  min-width: 320px;
  max-width: 420px;
  background: #fff;
  border-radius: 6px;
  box-shadow: 0 8px 20px rgba(16, 24, 40, 0.1);
  padding: 8px;
  outline: none;
}
.popup-arrow {
  position: absolute;
  width: 0;
  height: 0;
}
.popup-arrow.right {
  left: -8px;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  border-right: 8px solid #fff;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06));
}
.popup-arrow.left {
  right: -8px;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  border-left: 8px solid #fff;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06));
}
.popup-arrow.top {
  bottom: -8px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-top: 8px solid #fff;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06));
}
.popup-arrow.bottom {
  top: -8px;
  left: 50%;
  transform: translateX(-50%);
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-bottom: 8px solid #fff;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06));
}

.popup-content {
  padding: 8px;
}
.form-row {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}
.form-row label {
  width: 60px;
  font-size: 13px;
  color: #333;
  margin-right: 8px;
}
.popup-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 6px;
}

/* 表格溢出处理 */
.ant-table td,
.ant-table th {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
</style>
